/**************************************************************************
* This file is part of Hawkengine.
*
* Hawkengine is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Hawkengine is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Hawkengine.  If not, see <http://www.gnu.org/licenses/>.
**************************************************************************/


/**************************************************************************
* Dependencies
**************************************************************************/
#include "engine/keyboard.h"
#include "engine/render.h"
#include "engine/util.h"
#include <float.h>

/**************************************************************************
* Preprocessing
**************************************************************************/
#define MAX_KEYS_ON_PAGE 64
#define MAX_PAGES 4
#define KEYBOARD_SHIFT 12

/**************************************************************************
* Types
**************************************************************************/
typedef enum
{
  STYLE_NONE,
  STYLE_LINEAR,
  STYLE_QUADRATIC,
  STYLE_COS,
  STYLE_COUNT,
} style_type;

typedef struct
{
  char name;
  char sname;
  bounds_type bounds;
} key_type;

typedef struct
{
  int num_keys;
  key_type keys[MAX_KEYS_ON_PAGE];
  bounds_type bounds;
} page_type;

typedef struct
{
  char name[128];
  int active_page_idx;
  int num_pages;
  page_type pages[MAX_PAGES];
  bounds_type bounds;
} keyboard_type;

/**************************************************************************
* Variables
**************************************************************************/
bounds_type keyboard_bounds = {-1, 1, -1, 1};

/**************************************************************************
* Static Variables
**************************************************************************/
static bounds_type actual_keyboard_bounds = {-1, 1, -1, 1};
static keyboard_type default_keyboard;
static keyboard_type* active_keyboard;
static float active_pt[2];
static BOOL pt_active = FALSE;
static BOOL open = FALSE;
static float focus = 2;
static style_type current_style = STYLE_QUADRATIC;
static key_type* selected_key = NULL;
static BOOL shifted = FALSE;
static keyboard_response_func_type on_keyboard_response_func;

/**************************************************************************
* Function Prototypes
**************************************************************************/
static float f(float x, float x0, float x1, float x2);
static float fx(float x);
static float fy(float y);
static float F(float x, float x0, float x1, float x2);
static float Fx(float a, float b);
static float Fy(float a, float b);
static float g(float x, float x0, float x1, float x2);
static void get_point(float x, float y, float* point_x, float* point_y);
static void get_scale(float x, float y, float* scale_x, float* scale_y);

/**************************************************************************
* Function Implementations
**************************************************************************/
static float f(float x, float x0, float x1, float x2)
{
  // scale function
  // 1] f(x) = (-focus * g(x)) + focus
  // 2] f(x) = focus * (sqrt(2) - sqrt(1 + g(x)^2))
  // 3] f(x) = focus * cos(g(x) * pi) + focus
  if (pt_active) {
    float g_x = g(x, x0, x1, x2);
    switch (current_style) {
    case STYLE_LINEAR:
      return ((-focus * g_x) + focus);
    case STYLE_QUADRATIC:
      return (float)(focus * (sqrt(2.0) - sqrt(1 + (g_x * g_x))));
    case STYLE_COS:
      return ((focus * cos(g_x * PI)) + focus);
    default:
      break;
    }
  }
  return x;
}

static float fx(float x)
{
  return f(x, active_pt[X], active_keyboard->bounds.left, active_keyboard->bounds.right);
}

static float fy(float y)
{
  return f(y, active_pt[Y], active_keyboard->bounds.bottom, active_keyboard->bounds.top);
}

static float F(float x, float x0, float x1, float x2)
{
  // distance function -- integral of scale function
  // 1] F(x) = -focus * (x2 - x1) * g(x)^2 / 2.0 + focus * g(x)
  // 2] F(x) = focus (g(x) * sqrt(2) - 0.5 * (g(x) * sqrt(1 + g(x)^2) + asinh(x)))
  // 3] F(x) = focus * (x2 - x1) / pi * sin(g(x)) + focus * x
  // note: asinh(x) = ln(x + sqrt(1 + x^2))
  if (pt_active) {
    float g_x = g(x, x0, x1, x2);
    switch (current_style) {
    case STYLE_LINEAR:
      return ((-focus * (x2 - x1) * g_x * g_x / 2.0) + (focus * x));
    case STYLE_QUADRATIC:
      {
        float a = sqrt(1 + (g_x * g_x));
        return (focus * (x2 - x1) * ((g_x * sqrt(2.0)) - (0.5 * ((g_x * a) + log(g_x + a)))));
      }
    case STYLE_COS:
      return ((focus * (x2 - x1) / PI * sin(g_x * PI)) + (focus * x));
    default:
      break;
    }
  }
  return x;
}

static float Fx(float a, float b)
{
  return (F(b, active_pt[X], active_keyboard->bounds.left, active_keyboard->bounds.right) -
          F(a, active_pt[X], active_keyboard->bounds.left, active_keyboard->bounds.right));
}

static float Fy(float a, float b)
{
  return (F(b, active_pt[Y], active_keyboard->bounds.bottom, active_keyboard->bounds.top) -
          F(a, active_pt[Y], active_keyboard->bounds.bottom, active_keyboard->bounds.top));
}

static float g(float x, float x0, float x1, float x2)
{
  return ((x - x0) / (x2 - x1));
}

static void get_point(float x, float y, float* point_x, float* point_y)
{
  (*point_x) = Fx(active_keyboard->pages[active_keyboard->active_page_idx].bounds.left, x);
  (*point_y) = Fy(active_keyboard->pages[active_keyboard->active_page_idx].bounds.bottom, y);
}

static void get_scale(float x, float y, float* scale_x, float* scale_y)
{
  (*scale_x) = fx(x);
  (*scale_y) = fy(y);
}

void keyboard_close(void)
{
  open = FALSE;

  on_keyboard_response_func = NULL;
}

void keyboard_init(void)
{
  keyboard_type* k;
  int i, j;
 
  k = &default_keyboard;
  memset(k, 0, sizeof(keyboard_type));
  strcpy(k->name, "default");

  // main page
  // upper row
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].name = 'q';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].sname = 'Q';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.left = -5;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.right = -4;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.bottom = 1;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.top = 2;
  k->pages[k->num_pages].num_keys++;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].name = 'w';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].sname = 'W';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.left = -4;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.right = -3;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.bottom = 1;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.top = 2;
  k->pages[k->num_pages].num_keys++;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].name = 'e';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].sname = 'E';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.left = -3;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.right = -2;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.bottom = 1;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.top = 2;
  k->pages[k->num_pages].num_keys++;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].name = 'r';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].sname = 'R';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.left = -2;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.right = -1;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.bottom = 1;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.top = 2;
  k->pages[k->num_pages].num_keys++;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].name = 't';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].sname = 'T';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.left = -1;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.right = -0;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.bottom = 1;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.top = 2;
  k->pages[k->num_pages].num_keys++;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].name = 'y';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].sname = 'Y';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.left = 0;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.right = 1;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.bottom = 1;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.top = 2;
  k->pages[k->num_pages].num_keys++;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].name = 'u';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].sname = 'U';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.left = 1;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.right = 2;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.bottom = 1;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.top = 2;
  k->pages[k->num_pages].num_keys++;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].name = 'i';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].sname = 'I';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.left = 2;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.right = 3;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.bottom = 1;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.top = 2;
  k->pages[k->num_pages].num_keys++;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].name = 'o';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].sname = 'O';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.left = 3;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.right = 4;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.bottom = 1;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.top = 2;
  k->pages[k->num_pages].num_keys++;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].name = 'p';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].sname = 'P';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.left = 4;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.right = 5;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.bottom = 1;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.top = 2;
  k->pages[k->num_pages].num_keys++;

  // home row
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].name = 'a';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].sname = 'A';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.left = -5;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.right = -4;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.bottom = 0;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.top = 1;
  k->pages[k->num_pages].num_keys++;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].name = 's';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].sname = 'S';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.left = -4;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.right = -3;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.bottom = 0;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.top = 1;
  k->pages[k->num_pages].num_keys++;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].name = 'd';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].sname = 'D';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.left = -3;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.right = -2;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.bottom = 0;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.top = 1;
  k->pages[k->num_pages].num_keys++;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].name = 'f';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].sname = 'F';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.left = -2;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.right = -1;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.bottom = 0;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.top = 1;
  k->pages[k->num_pages].num_keys++;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].name = 'g';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].sname = 'G';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.left = -1;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.right = 0;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.bottom = 0;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.top = 1;
  k->pages[k->num_pages].num_keys++;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].name = 'h';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].sname = 'H';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.left = 0;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.right = 1;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.bottom = 0;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.top = 1;
  k->pages[k->num_pages].num_keys++;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].name = 'j';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].sname = 'J';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.left = 1;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.right = 2;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.bottom = 0;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.top = 1;
  k->pages[k->num_pages].num_keys++;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].name = 'k';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].sname = 'K';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.left = 2;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.right = 3;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.bottom = 0;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.top = 1;
  k->pages[k->num_pages].num_keys++;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].name = 'l';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].sname = 'L';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.left = 3;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.right = 4;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.bottom = 0;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.top = 1;
  k->pages[k->num_pages].num_keys++;

  // lower row
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].name = 'z';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].sname = 'Z';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.left = -5;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.right = -4;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.bottom = -1;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.top = 0;
  k->pages[k->num_pages].num_keys++;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].name = 'x';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].sname = 'X';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.left = -4;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.right = -3;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.bottom = -1;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.top = 0;
  k->pages[k->num_pages].num_keys++;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].name = 'c';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].sname = 'C';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.left = -3;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.right = -2;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.bottom = -1;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.top = 0;
  k->pages[k->num_pages].num_keys++;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].name = 'v';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].sname = 'V';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.left = -2;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.right = -1;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.bottom = -1;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.top = 0;
  k->pages[k->num_pages].num_keys++;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].name = 'b';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].sname = 'B';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.left = -1;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.right = 0;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.bottom = -1;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.top = 0;
  k->pages[k->num_pages].num_keys++;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].name = 'n';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].sname = 'N';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.left = 0;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.right = 1;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.bottom = -1;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.top = 0;
  k->pages[k->num_pages].num_keys++;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].name = 'm';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].sname = 'M';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.left = 1;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.right = 2;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.bottom = -1;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.top = 0;
  k->pages[k->num_pages].num_keys++;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].name = ',';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].sname = '<';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.left = 2;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.right = 3;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.bottom = -1;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.top = 0;
  k->pages[k->num_pages].num_keys++;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].name = '.';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].sname = '>';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.left = 3;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.right = 4;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.bottom = -1;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.top = 0;
  k->pages[k->num_pages].num_keys++;

  // special keys
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].name = KEYBOARD_SHIFT;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].sname = KEYBOARD_SHIFT;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.left = -5;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.right = -4;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.bottom = -2;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.top = -1;
  k->pages[k->num_pages].num_keys++;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].name = KEYBOARD_RETURN;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].sname = KEYBOARD_RETURN;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.left = 4;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.right = 5;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.bottom = -1;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.top = 1;
  k->pages[k->num_pages].num_keys++;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].name = ' ';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].sname = ' ';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.left = -2;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.right = 2;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.bottom = -2;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.top = -1;
  k->pages[k->num_pages].num_keys++;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].name = '<';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].sname = '<';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.left = -4;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.right = -2;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.bottom = -2;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.top = -1;
  k->pages[k->num_pages].num_keys++;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].name = '>';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].sname = '>';
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.left = 2;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.right = 4;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.bottom = -2;
  k->pages[k->num_pages].keys[k->pages[k->num_pages].num_keys].bounds.top = -1;
  k->pages[k->num_pages].num_keys++;

  k->num_pages++;

  // set up page bounds
  for (i = 0; i < k->num_pages; i++) {
    k->pages[i].bounds.left = FLT_MAX;
    k->pages[i].bounds.right = -FLT_MAX;
    k->pages[i].bounds.bottom = FLT_MAX;
    k->pages[i].bounds.top = -FLT_MAX;
    for (j = 0; j < k->pages[i].num_keys; j++) {
      _UPDATE_MIN(k->pages[i].bounds.left, k->pages[i].keys[j].bounds.left);
      _UPDATE_MAX(k->pages[i].bounds.right, k->pages[i].keys[j].bounds.right);
      _UPDATE_MIN(k->pages[i].bounds.bottom, k->pages[i].keys[j].bounds.bottom);
      _UPDATE_MAX(k->pages[i].bounds.top, k->pages[i].keys[j].bounds.top);
    }
  }
 
  // set up keyboard bounds
  k->bounds.left = FLT_MAX;
  k->bounds.right = -FLT_MAX;
  k->bounds.bottom = FLT_MAX;
  k->bounds.top = -FLT_MAX;
  for (i = 0; i < k->num_pages; i++) {
    _UPDATE_MIN(k->bounds.left, k->pages[i].bounds.left);
    _UPDATE_MAX(k->bounds.right, k->pages[i].bounds.right);
    _UPDATE_MIN(k->bounds.bottom, k->pages[i].bounds.bottom);
    _UPDATE_MAX(k->bounds.top, k->pages[i].bounds.top);
  }

  active_keyboard = &default_keyboard;
}

void keyboard_open(keyboard_response_func_type keyboard_response_func)
{
  open = TRUE;

  on_keyboard_response_func = keyboard_response_func;
}

BOOL keyboard_opened(void)
{
  return open;
}

void keyboard_render(const bounds_type* bounds)
{
  UNUSED_PARAM(bounds);
  char buffer[16];
  page_type* active_page;
  float scale[2], pt[2];
  int i;

  if (!active_keyboard) {
    return;
  }

  if (pt_active) {
    memset(&actual_keyboard_bounds, 0, sizeof(actual_keyboard_bounds));
    actual_keyboard_bounds.right = Fx(active_keyboard->bounds.left,
                                      active_keyboard->bounds.right);
    actual_keyboard_bounds.top = Fy(active_keyboard->bounds.bottom,
                                    active_keyboard->bounds.top);
  } else {
    actual_keyboard_bounds = active_keyboard->bounds;
  }

  // put keyboard in bottom half
  keyboard_bounds = actual_keyboard_bounds;
  keyboard_bounds.top += bounds_height(&keyboard_bounds);

  glPushMatrix();
  render_set_view_ortho(&keyboard_bounds);

  render_set_color(&OVERLAY_COLOR);
  render_rect(&actual_keyboard_bounds, TRUE);

  scale[X] = scale[Y] = 1;
  active_page = &(active_keyboard->pages[active_keyboard->active_page_idx]);
  for (i = 0; i < active_page->num_keys; i++) {
    bounds_center(&(active_page->keys[i].bounds), &(pt[X]), &(pt[Y]));
    if (pt_active) {
      get_scale(pt[X], pt[Y], &(scale[X]), &(scale[Y]));
      get_point(pt[X], pt[Y], &(pt[X]), &(pt[Y]));
    }
 
    glPushMatrix();
    glTranslatef(pt[X], pt[Y], 0);
    glScalef((scale[X] * bounds_width(&(active_page->keys[i].bounds))),
             (scale[Y] * bounds_height(&(active_page->keys[i].bounds))), 1);

    switch (active_page->keys[i].name) {
    case KEYBOARD_RETURN:
      strcpy(buffer, "Ret");
      break;
    case KEYBOARD_SHIFT:
      if (shifted) {
        strcpy(buffer, "Aa");
      } else {
        strcpy(buffer, "aA");
      }
      break;
    default:
      {
        if (shifted) {
          buffer[0] = active_page->keys[i].sname;
          buffer[1] = 0;
        } else {
          buffer[0] = active_page->keys[i].name;
          buffer[1] = 0;
        }
      }
      break;
    }

    if (selected_key == &(active_page->keys[i])) {
      render_button(&UNIT_BOUNDS, buffer, &(ACTIVE_TEXT_COLORS[FALSE]),
                    &OVERLAY_COLOR);
    } else {
      render_button(&UNIT_BOUNDS, buffer, &OVERLAY_COLOR,
                    &(ACTIVE_TEXT_COLORS[FALSE]));
    }

    glPopMatrix();
  }

  glPopMatrix();
}

void keyboard_touch_move(const touch_type* touch)
{
  int i;

  if (touch->count <= 0) {
    return;
  }

  pt_active = bounds_contain(&actual_keyboard_bounds,
                             touch->locs[0][X], touch->locs[0][Y]);
  selected_key = NULL;
  if (pt_active) {
    page_type* active_page;

    active_pt[X] = touch->locs[0][X];
    active_pt[Y] = touch->locs[0][Y];

    // move point back to the actual (unscaled) keyboard bounds
    bounds_convert_xy(&actual_keyboard_bounds, &(active_keyboard->bounds),
                      &(active_pt[X]), &(active_pt[Y]));
 
    active_page = &(active_keyboard->pages[active_keyboard->active_page_idx]);
    for (i = 0; i < active_page->num_keys; i++) {
      if (bounds_contain(&(active_page->keys[i].bounds),
                         active_pt[X], active_pt[Y])) {
        selected_key = &(active_page->keys[i]);
        break;
      }
    }
  }
}

void keyboard_touch_start(const touch_type* touch)
{
  keyboard_touch_move(touch);
}

void keyboard_touch_stop(const touch_type* touch)
{
  keyboard_touch_move(touch);

  if (selected_key && on_keyboard_response_func) {
    if (selected_key->name == KEYBOARD_SHIFT) {
      _TOGGLE(shifted);
    } else if (shifted) {
      on_keyboard_response_func(selected_key->sname);
    } else {
      on_keyboard_response_func(selected_key->name);
    }
  }

  pt_active = FALSE;
  selected_key = NULL;
}
